package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.orders.dto.response.OrderResDTO;
import com.jzo2o.api.orders.dto.response.OrderSimpleResDTO;
import com.jzo2o.common.enums.EnableStatusEnum;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.utils.JsonUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.handler.OrdersHandler;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.service.IOrdersCanceledService;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.IOrdersManagerService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.jzo2o.redis.helper.CacheHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jzo2o.orders.base.constants.FieldConstants.SORT_BY;
import static com.jzo2o.orders.base.constants.RedisConstants.RedisKey.ORDERS;
import static com.jzo2o.orders.base.constants.RedisConstants.Ttl.ORDERS_PAGE_TTL;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersManagerServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersManagerService {

    @Resource
    private IOrdersCanceledService ordersCanceledService;

    @Resource
    private IOrdersCommonService ordersCommonService;

    @Resource
    private IOrdersCreateService ordersCreateService;

    @Resource
    private IOrdersRefundService ordersRefundService;

    @Resource
    private OrdersHandler ordersHandler;

    @Resource
    private OrderStateMachine orderStateMachine;

    @Resource
    private CacheHelper cacheHelper;

    @Override
    public List<Orders> batchQuery(List<Long> ids) {
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery().in(Orders::getId, ids).ge(Orders::getUserId, 0);
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public Orders queryById(Long id) {
        return baseMapper.selectById(id);
    }

    /**
     * 滚动分页查询
     *
     * @param currentUserId 当前用户id
     * @param ordersStatus  订单状态，0：待支付，100：派单中，200：待服务，300：服务中，400：待评价，500：订单完成，600：已取消，700：已关闭
     * @param sortBy        排序字段
     * @return 订单列表
     */
    @Override
    public List<OrderSimpleResDTO> consumerQueryList(Long currentUserId, Integer ordersStatus, Long sortBy) {

        //1.构件查询条件(拿到id信息，select id from table where **)
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
                .eq(ObjectUtils.isNotNull(ordersStatus), Orders::getOrdersStatus, ordersStatus)
                .lt(ObjectUtils.isNotNull(sortBy), Orders::getSortBy, sortBy)
                .eq(Orders::getUserId, currentUserId)
                .eq(Orders::getDisplay, EnableStatusEnum.ENABLE.getStatus())
                .select(Orders::getId);
        Page<Orders> queryPage = new Page<>();
        queryPage.addOrder(OrderItem.desc(SORT_BY));
        queryPage.setSearchCount(false);

        //2.查询订单列表
        Page<Orders> ordersPage = baseMapper.selectPage(queryPage, queryWrapper);
        List<Orders> records = ordersPage.getRecords();
        if (CollectionUtil.isEmpty(records)) {
            return Collections.emptyList();
        }

        //3.拿到所有id集合(一部分是缓存中有的，一部分是缓存没有的，所以需要将缓存中没有的数据额外去DB查一次)
        List<Long> ids = records.stream().map(Orders::getId).collect(Collectors.toList());

        //4.拼接查找Redis-hash数据的大key
        String key = String.format(ORDERS, currentUserId);

        //5.查询redis缓存数据
        /**
         * key：hash的大key
         * ids：hash的小key(订单id列表)
         * batchDataQueryExecutor：你给我一个ids集合，如果我的缓存中没有，我就会返回没有命中缓存的nocaheIds
         *  这个执行器，就会用当前没有命中的id集合去数据库查询一次
         * clazz：缓存中返回的数据对象应该是什么样
         * ttl：缓存过期时间
         */
        List<OrderSimpleResDTO> orderSimpleResDTOS = cacheHelper.batchGet(key, ids, (nocaheIds, clazz) -> {
            // 5.1 如果缓存没有就查询数据库
            List<Orders> orders = batchQuery(nocaheIds);
            // 5.2 避免缓存穿透所以缓存空值
            if (CollectionUtil.isEmpty(orders)) {
                return new HashMap<>();
            }
            // 5.3 换数据并返回
            return orders.stream().collect(Collectors.toMap(Orders::getId, o -> BeanUtil.toBean(o, OrderSimpleResDTO.class)));
        }, OrderSimpleResDTO.class, ORDERS_PAGE_TTL);

        return orderSimpleResDTOS;

    }
    /**
     * 根据订单id查询
     *
     * @param id 订单id
     * @return 订单详情
     */
    @Override
    public OrderResDTO getDetail(Long id) {

        // 1-用户查看订单详情
        String currentSnapshot = orderStateMachine.getCurrentSnapshotCache(id.toString());
        OrderSnapshotDTO snapshot = JsonUtils.toBean(currentSnapshot, OrderSnapshotDTO.class);

        // 2-懒加载方式做超时未支付订单的取消
        snapshot = cancelIfOverTime(snapshot);

        OrderResDTO orderResDTO = BeanUtil.toBean(snapshot, OrderResDTO.class);
        return orderResDTO;
    }

    /**
     * 取消超时15min未支付的订单
     * @param snapshot
     * @return
     */
    private OrderSnapshotDTO cancelIfOverTime(OrderSnapshotDTO snapshot) {
        // 1-代码健壮性判断
        if (snapshot.getOrdersStatus().equals(OrderStatusEnum.NO_PAY.getStatus())
                && snapshot.getCreateTime().plusMinutes(15).isBefore(LocalDateTime.now())) {
            // 避免从列表点击进去详情的瞬间，有人支付成功
            OrdersPayResDTO tradServer = ordersCreateService.getPayResultFromTradServer(snapshot.getId());
            if (tradServer.getPayStatus() != OrderPayStatusEnum.PAY_SUCCESS.getStatus()) {
                OrderCancelDTO orderCancelDTO = BeanUtil.toBean(snapshot, OrderCancelDTO.class);
                orderCancelDTO.setCurrentUserType(UserContext.currentUser().getUserType());
                orderCancelDTO.setCurrentUserId(UserContext.currentUserId());
                orderCancelDTO.setCurrentUserName(UserContext.currentUser().getName());
                orderCancelDTO.setCancelReason("订单超时自动取消Herb");
                cancelOrder(orderCancelDTO);

                // 从数据库快照表查询一次最新状态
                String currentSnapshot = orderStateMachine.getCurrentSnapshotCache(snapshot.getId().toString());
                snapshot = JsonUtils.toBean(currentSnapshot, OrderSnapshotDTO.class);
            }
        }
        return snapshot;
    }

    /**
     * 订单评价
     *
     * @param ordersId 订单id
     */
    @Override
    @Transactional
    public void evaluationOrder(Long ordersId) {
//        //查询订单详情
//        Orders orders = queryById(ordersId);
//
//        //构建订单快照
//        OrderSnapshotDTO orderSnapshotDTO = OrderSnapshotDTO.builder()
//                .evaluationTime(LocalDateTime.now())
//                .build();
//
//        //订单状态变更
//        orderStateMachine.changeStatus(orders.getUserId(), orders.getId().toString(), OrderStatusChangeEventEnum.EVALUATE, orderSnapshotDTO);
    }

    /**
     * 订单取消接口方法
     *
     * @param orderCancelDTO 取消对象
     */
    @Override
    public void cancelOrder(OrderCancelDTO orderCancelDTO) {

        // 1-健壮性校验
        Orders orders = getById(orderCancelDTO.getId());
        if (ObjectUtils.isEmpty(orders)) {
            throw new DbRuntimeException("订单查询为空");
        }

        // 2-获取订单当前状态
        Integer ordersStatus = orders.getOrdersStatus();
        // 2.1 订单未支付：直接取消
        if (ordersStatus.equals(OrderStatusEnum.NO_PAY.getStatus())) {
            log.error("[待支付订单取消]取消订单id：{}， 订单：{}", orders.getId(), orders);
            cancelNoPay(orderCancelDTO, orders);
        } else if (ordersStatus.equals(OrderStatusEnum.DISPATCHING.getStatus())) {
            // 2.2 订单已支付：取消+退款
            log.error("[已支付订单取消]取消订单id：{}， 订单：{}", orders.getId(), orders);
            cancelByDispatching(orderCancelDTO, orders.getTradingOrderNo(), orders.getRealPayAmount());
            // 新启动一个线程立马退款
            ordersHandler.requestRefundNewThread(orders.getId());
        } else {
            throw new CommonException("当前状态不允许取消订单");
        }
    }

    /**
     * 新增取消订单/更新订单状态/新增退款记录
     * @param orderCancelDTO
     * @param tradingOrderNo
     * @param realPayAmount
     */
    @Override
    @Transactional
    public void cancelByDispatching(OrderCancelDTO orderCancelDTO, Long tradingOrderNo, BigDecimal realPayAmount) {

        // 1-新增取消订单数据
        OrdersCanceled insertEntity = BeanUtil.toBean(orderCancelDTO, OrdersCanceled.class);
        insertEntity.setCancellerId(orderCancelDTO.getCurrentUserId());
        insertEntity.setCancelerName(orderCancelDTO.getCurrentUserName());
        insertEntity.setCancellerType(orderCancelDTO.getCurrentUserType());
        insertEntity.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(insertEntity);

        // 2-更新订单状态：派单中-->已关闭
        OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO
                .builder()
                .id(orderCancelDTO.getId())
                // 当订单是这个状态，我才能够去更新，CAS思想
                .originStatus(OrderStatusEnum.DISPATCHING.getStatus())
                // 更新成对应的目标状态
                .targetStatus(OrderStatusEnum.CLOSED.getStatus())
                .refundStatus(OrderRefundStatusEnum.REFUNDING.getStatus())
                .build();
        Integer rows = ordersCommonService.updateStatus(orderUpdateStatusDTO);
        if (rows <= 0 ) {
            throw new CommonException("更新派单中订单失败");
        }

        // 3-新增退款记录
        OrdersRefund ordersRefund = new OrdersRefund();
        // 用户已支付场景下，需要取消订单+退订单，怎么关联呢：让两张表的主键一样
        ordersRefund.setId(insertEntity.getId());
        ordersRefund.setTradingOrderNo(tradingOrderNo);
        ordersRefund.setRealPayAmount(realPayAmount);
        ordersRefundService.save(ordersRefund);
    }

    /**
     * 取消未支付订单：更新订单状态 + 创建一条取消订单数据
     * @param orderCancelDTO    前端取消订单参数
     * @param orders            当前本地已创建好的订单信息
     */
    @Transactional
    public void cancelNoPay(OrderCancelDTO orderCancelDTO, Orders orders) {

        // 1-更新订单状态
        /**
         * 原始：update orders set order_status=600 where id = #{id}，会产生ABA问题
         *  线程1：0->600
         *  线程2：600->600
         *  线程3：线程1执行完，线程2阻塞中开始执行，此时订单状态是600(已取消)，假如说我们后续可以让取消的订单重新支付，已取消->待支付->支付成功
         * 乐观锁版本：update orders set order_status=600 where id = #{id} and order_status=0
         */
        OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO
                .builder()
                .id(orderCancelDTO.getId())
                // 当订单是这个状态，我才能够去更新，CAS思想
                .originStatus(OrderStatusEnum.NO_PAY.getStatus())
                // 更新成对应的目标状态
                .targetStatus(OrderStatusEnum.CANCELED.getStatus())
                .build();
        Integer rows = ordersCommonService.updateStatus(orderUpdateStatusDTO);
        if (rows <= 0 ) {
            throw new CommonException("更新订单失败");
        }

        // 2-新增一条取消订单数据
        OrdersCanceled insertEntity = BeanUtil.toBean(orderCancelDTO, OrdersCanceled.class);
        insertEntity.setCancellerId(orderCancelDTO.getCurrentUserId());
        insertEntity.setCancelerName(orderCancelDTO.getCurrentUserName());
        insertEntity.setCancellerType(orderCancelDTO.getCurrentUserType());
        insertEntity.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(insertEntity);
    }

}
